package com.eshipenet.shipnet.equipmentdatahub

import play.api.libs.json.{JsBoolean, JsNull, JsNumber, JsString, JsValue, Json}

import scala.util.{Failure, Try}

object DataType {
  private val maps = Map[String, DataType](
    "STR"    -> DataTypeString,
    "STRING" -> DataTypeString,
    "INT"    -> DataTypeInt,
    "FLOAT"  -> DataTypeFloat,
    "BOOL"   -> DataTypeBool,
  )
  def parse(dataType: String): Try[DataType] =
    maps.get(dataType.toUpperCase).map(Try(_)).getOrElse(Failure(new Exception(s"invalid dataType name: $dataType")))
}

trait DataType {
  def fromJValue(value: JsValue): Try[JsValue]
}

case object DataTypeString extends DataType {
  override def fromJValue(value: JsValue): Try[JsValue] = Try {
    value match {
      case v: JsString    => v
      case v: JsNull.type => v
      case x              => JsString(Json.stringify(x))
    }
  }
}

case object DataTypeInt extends DataType {

  override def fromJValue(value: JsValue): Try[JsValue] = Try {
    value match {
      case JsNumber(v) => if (v.isValidLong) JsNumber(v) else JsNumber(v.toLong)
      case JsString(v) => JsNumber(v.toLong)
      case _           => throw new Exception(s"cannot coerce $value to int")
    }
  }

}

case object DataTypeFloat extends DataType {

  override def fromJValue(value: JsValue): Try[JsValue] = Try {
    val v = value match {
      case v: JsNumber => v
      case JsString(s) => JsNumber(s.toDouble)
      case _           => throw new Exception(s"cannot coerce $value to float")
    }
    println(s"float = $value")
    v
  }

}

case object DataTypeBool extends DataType {

  private val trues  = Set("TRUE", "T", "YES", "Y", "1")
  private val falses = Set("FALSE", "F", "NO", "N", "0")

  override def fromJValue(value: JsValue): Try[JsValue] = Try {
    value match {
      case v: JsBoolean => v
      case JsNumber(v)  => JsBoolean(v != 0)
      case JsString(s) =>
        if (trues.contains(s.toUpperCase)) {
          JsBoolean(true)
        } else if (falses.contains(s.toUpperCase)) {
          JsBoolean(false)
        } else {
          throw new Exception(s"cannot coerce $value to bool")
        }
      case _ => throw new Exception(s"cannot coerce $value to bool")
    }
  }

}
